/* 
 * 	This file is part of VPN Monitor Project 7..

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package monitor.gui;

import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;

import javax.swing.JButton;
import javax.swing.JOptionPane;

import monitor.Monitor;
import monitor.gui.Start.StartStop;
import monitor.interfaces.observers.TrafficObserver;
import monitor.interfaces.observers.TrafficStringObserver;
import monitor.interfaces.observers.TrafficBufferObserver;
import monitor.utility.SerializationIO;
public class GuiControl implements ActionListener, WindowListener{
	private LinkedList<ColorObserver> observers = null;
	private LinkedList<String> logEntrys = null;
	private EnumMap<SettingsEnum, String> settings = null;
	private final static String FILE_NAME = "settings.data";
	final static String APPLICATION_DELIMITER = "#";
	private Log log = null;
	private Gui gui = null;
	private Monitor monitor = null;
	
		
	public GuiControl(Monitor monitor){
		this.monitor = monitor;
		observers = new LinkedList<ColorObserver>();
		settings = loadSettings();
		gui = new Gui(this, settings);
		log = gui.getLog();
		if(logEntrys!=null){
			for(String s : logEntrys)
				addLogEntry(s);
			logEntrys = null;
		}
	}
	
	private EnumMap<SettingsEnum, String> loadSettings(){
		try {
			addLogEntry("Loading settings from "+FILE_NAME);
			return (new SerializationIO<EnumMap<SettingsEnum, String>>()).readClassObject(FILE_NAME);
		} catch (FileNotFoundException e) {
			addLogEntry("Loading failed, file not found, loading default settings");
			//e.printStackTrace();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		EnumMap<SettingsEnum, String> settings = new EnumMap<SettingsEnum, String>(SettingsEnum.class);
		for(SettingsEnum type : SettingsEnum.values()){
			if(type.getName().startsWith("INTERFACE_"))
				continue;
			settings.put(type, SettingsEnum.DEFAULT[type.i]);
		}
		return settings;
	}
	
	private EnumMap<SettingsEnum, String> getSettings(){
		EnumMap<SettingsEnum, String> settings = new EnumMap<SettingsEnum, String>(SettingsEnum.class);
		
		for(SettingsEnum se : SettingsEnum.CHANGEABLE)
			settings.put(se, this.settings.get(se));
		
		for(Map.Entry<SettingsEnum, String> e : this.settings.entrySet())
			if(e.getKey().getName().contains("COLOR_"))
				settings.put(e.getKey(), e.getValue());
		
		for(Map.Entry<SettingsEnum, Boolean> e : gui.getLimitsUsed().entrySet())
			settings.put(e.getKey(), e.getValue().toString());
		
		for(Map.Entry<SettingsEnum, Double> e : gui.getLimitsLevel().entrySet())
			settings.put(e.getKey(), e.getValue().toString());
		
		for(Map.Entry<SettingsEnum, Double> e : gui.getLogLevels().entrySet())
			settings.put(e.getKey(), e.getValue().toString());
		
		for(Map.Entry<SettingsEnum, Double> e : gui.getTimerLevels().entrySet())
				settings.put(e.getKey(), e.getValue().toString());
		
		settings.put(SettingsEnum.APPLICATION, gui.getApplications());
		settings.put(SettingsEnum.SAMPLE_DIAGRAM, Integer.toString(gui.getDiagramSample()));
		
		return settings;
	}
	
	private void storeSettings(){
		try {
			addLogEntry("Saving settings to file "+FILE_NAME);			
			(new SerializationIO<EnumMap<SettingsEnum, String>>()).writeClassObject(getSettings(), FILE_NAME, true);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public void addLogEntry(String entry){
		if(log==null){
			if(logEntrys==null)
				logEntrys = new LinkedList<String>();
			logEntrys.add(entry);
		} else
			log.addLogEntry(entry);
	}
	
	public void halt(String source){
		gui.changeStartStop(StartStop.START);
		addLogEntry(source+", halted monitor.");
	}
	
	private void exit(String source){
		monitor.exit(source);
	}
	
	private void start(){		
		HashMap<String, Boolean> limitActive = new HashMap<String, Boolean>();
		for(Map.Entry<SettingsEnum, Boolean> e : gui.getLimitsUsed().entrySet())
			limitActive.put(e.getKey().getName().substring("LIMIT_BOX_".length()), e.getValue());
		
		final double factorMB = 1e6;
		HashMap<String, Long> limitValue = new HashMap<String, Long>();
		for(Map.Entry<SettingsEnum, Double> e : gui.getLimitsLevel().entrySet())
			limitValue.put(e.getKey().getName().substring("LIMIT_SPINNER_".length()), (long) (e.getValue()*factorMB));
		
		HashMap<String, Long> logValue = new HashMap<String, Long>();
		for(Map.Entry<SettingsEnum, Double> e : gui.getLogLevels().entrySet())
			logValue.put(e.getKey().getName().substring("LOG_".length()), (long) (e.getValue()*factorMB));
		
		final double factorMS = 1e3;
		HashMap<String, Long> timerValue = new HashMap<String, Long>();
		for(Map.Entry<SettingsEnum, Double> e : gui.getTimerLevels().entrySet())
			timerValue.put(e.getKey().getName(), (long) (e.getValue()*factorMS));
		
		gui.changeStartStop(StartStop.STOP);
		settings.putAll(getSettings());
		monitor.start(gui.getNetworks(),
				limitActive,
				limitValue,
				logValue,
				timerValue,
				gui.getDiagramSample(),
				getApplication(gui.getApplications(), true));
	}
		
	private String[] getApplication(String applicationString, boolean start){
		if(start)
			return extractApplications(applicationString);
		else if(!applicationString.equals(settings.get(SettingsEnum.APPLICATION)))
			return extractApplications(applicationString);
		else
			return null;
	}
	
	private String[] extractApplications(String applicationString){
		ArrayList<String> applications = new ArrayList<String>();
		Collections.addAll(applications, applicationString.split(GuiControl.APPLICATION_DELIMITER));
		Iterator<String> it = applications.iterator();
		while(it.hasNext())
			if(it.next().length()==0)
				it.remove();
		if(applications.size()==0)
			return null;
		else
			return applications.toArray(new String[applications.size()]);
	}
	
	private void updateSettings(){
		if(!monitor.isRunning()){
			addLogEntry("Can't updated when monitor isn't running.");
			return;
		}
		
		HashMap<String, Boolean> limitActive = new HashMap<String, Boolean>();
		for(Map.Entry<SettingsEnum, Boolean> e : gui.getLimitsUsed().entrySet()){
			boolean state = Boolean.parseBoolean(settings.get(e.getKey()));
			if(state != e.getValue()){
				limitActive.put(e.getKey().getName().substring("LIMIT_BOX_".length()), e.getValue());
				settings.put(e.getKey(), e.getValue().toString());
			}
		}
		if(!limitActive.isEmpty())
			monitor.changeActiveLimits(limitActive);
		
		final double factorMB = 1e6;
		HashMap<String, Long> limitValue = new HashMap<String, Long>();
		for(Map.Entry<SettingsEnum, Double> e : gui.getLimitsLevel().entrySet()){
			if(Double.parseDouble(settings.get(e.getKey())) != e.getValue()){
				limitValue.put(e.getKey().getName().substring("LIMIT_SPINNER_".length()), (long) (e.getValue()*factorMB));
				settings.put(e.getKey(), e.getValue().toString());
			}
		}
		if(!limitValue.isEmpty())
			monitor.changeLimits(limitValue);
		
		HashMap<String, Long> logValue = new HashMap<String, Long>();
		for(Map.Entry<SettingsEnum, Double> e : gui.getLogLevels().entrySet())
			logValue.put(e.getKey().getName(), (long) (e.getValue()*factorMB));
		
		final double factorMS = 1e3;
		HashMap<String, Long> timerValue = new HashMap<String, Long>();
		for(Map.Entry<SettingsEnum, Double> e : gui.getTimerLevels().entrySet()){
			if(Double.parseDouble(settings.get(e.getKey())) != e.getValue()){
				timerValue.put(e.getKey().getName(), (long) (e.getValue()*factorMS));
				settings.put(e.getKey(), e.getValue().toString());
			}
		}
		if(!timerValue.isEmpty())
			monitor.changeTimers(timerValue);
		
		// Sample test
		if (Integer.parseInt(settings.get(SettingsEnum.SAMPLE_DIAGRAM)) != gui.getDiagramSample()){
			settings.put(SettingsEnum.SAMPLE_DIAGRAM, Integer.toString(gui.getDiagramSample()));
		}
		
		String applicationString = gui.getApplications();
		monitor.addRemoveApplication(getApplication(applicationString, false));
		settings.put(SettingsEnum.APPLICATION, applicationString);
	}
	
	synchronized void addTrafficUtilityObserver(TrafficBufferObserver ob){
		monitor.addTrafficUtilityObserver(ob);
	}
	
	synchronized void addTrafficStringObserver(TrafficStringObserver ob){
		monitor.addTrafficStringObserver(ob);
	}
	
	synchronized void addTrafficObserver(TrafficObserver ob){
		monitor.addTrafficObserver(ob);
	}
	
	synchronized void addColorObserver(ColorObserver ob){
		observers.add(ob);
	}
	
	synchronized void removeColorObserver(ColorObserver ob){
		observers.remove(ob);
	}
	
	synchronized void changeColor(SettingsEnum type, Color color){
		if(!type.getName().startsWith("COLOR_"))
			throw new IllegalArgumentException(type.toString()+" isn't a color.");
		addLogEntry(type.toString() + " changed to color #"+Integer.toHexString(color.getRGB()).substring(2).toUpperCase());
		settings.put(type, SettingsEnum.colorRGB(color));
		notifyColorChange(type, color);
	}
	
	synchronized void restoreColor(SettingsEnum type){
		addLogEntry(type.toString()+" restored to default");
		settings.put(type, SettingsEnum.DEFAULT[type.i]);
		notifyColorChange(type, SettingsEnum.defaultColor(type));
	}
	
	private synchronized void notifyColorChange(SettingsEnum type, Color color){
		synchronized(this){
			for(ColorObserver ob : observers)
				ob.colorUpdated(type, color);
		}
	}
	
	@Override
	public void actionPerformed(ActionEvent ae) {
		String buttonName = ((JButton) ae.getSource()).getName().toLowerCase();
		if(buttonName.equalsIgnoreCase("exit"))
			exit("User");
		else if(buttonName.equalsIgnoreCase(StartStop.START.toString()))
			start();
		else if(buttonName.equalsIgnoreCase(StartStop.STOP.toString()))
			monitor.halt("User");
		else if(buttonName.equalsIgnoreCase("update"))
			updateSettings();
		else if(buttonName.equalsIgnoreCase("save"))
			storeSettings();
	}
	
	@Override
	public void windowClosing(WindowEvent we) {
		int choise = JOptionPane.showConfirmDialog(gui, "Quit Monitor?", "Quit?", JOptionPane.OK_CANCEL_OPTION);
		if(JOptionPane.OK_OPTION == choise)
			exit("User");
	}
	
	enum SettingsEnum{
		COLOR_MESSAGE(0), COLOR_TIMESTAMP(1), COLOR_FOREGROUND(2), COLOR_BACKGROUND(3),
		COLOR_DOWNLOAD(4), COLOR_UPLOAD(5), COLOR_TOTAL(6),

		LIMIT_SPINNER_SESSION_DOWNLOAD(7), LIMIT_SPINNER_SESSION_UPLOAD(8), LIMIT_SPINNER_SESSION_TOTAL(9), 
		LIMIT_SPINNER_TODAY_DOWNLOAD(10), LIMIT_SPINNER_TODAY_UPLOAD(11), LIMIT_SPINNER_TODAY_TOTAL(12), 
		LIMIT_SPINNER_WEEK_DOWNLOAD(13), LIMIT_SPINNER_WEEK_UPLOAD(14), LIMIT_SPINNER_WEEK_TOTAL(15), 
		LIMIT_SPINNER_MONTH_DOWNLOAD(16), LIMIT_SPINNER_MONTH_UPLOAD(17), LIMIT_SPINNER_MONTH_TOTAL(18), 
		LIMIT_SPINNER_YEAR_DOWNLOAD(19), LIMIT_SPINNER_YEAR_UPLOAD(20), LIMIT_SPINNER_YEAR_TOTAL(21), 

		LIMIT_BOX_SESSION_DOWNLOAD(22), LIMIT_BOX_SESSION_UPLOAD(23), LIMIT_BOX_SESSION_TOTAL(24), 
		LIMIT_BOX_TODAY_DOWNLOAD(25), LIMIT_BOX_TODAY_UPLOAD(26), LIMIT_BOX_TODAY_TOTAL(27), 
		LIMIT_BOX_WEEK_DOWNLOAD(28), LIMIT_BOX_WEEK_UPLOAD(29), LIMIT_BOX_WEEK_TOTAL(30), 
		LIMIT_BOX_MONTH_DOWNLOAD(31), LIMIT_BOX_MONTH_UPLOAD(32), LIMIT_BOX_MONTH_TOTAL(33), 
		LIMIT_BOX_YEAR_DOWNLOAD(34), LIMIT_BOX_YEAR_UPLOAD(35), LIMIT_BOX_YEAR_TOTAL(36), 
		
		LOG_DOWNLOAD(37), LOG_UPLOAD(38), LOG_TOTAL(39),
		
		TIMER_MONITOR(40), TIMER_TRAFFIC(41),
		
		SAMPLE_DIAGRAM(42),
		
		APPLICATION(43),
		
		INTERFACE_INTERFACE(44), INTERFACE_NETWORK(45);
		
		private final int i;
		
		private SettingsEnum(int i){
			this.i=i;
		}
		
		private final static String[] DEFAULT = {
			"0 0 0", "192 192 192", "255 255 0", "0 0 0", "0 255 0", "255 0 0", "0 0 255", 
			
			"1.0", "1.0", "1.0", "1.0", "1.0", "1.0", 
			"1.0", "1.0", "1.0", "1.0", "1.0", "1.0", 
			"1.0", "1.0", "1.0",
			
			"FALSE", "FALSE", "FALSE", "FALSE", "FALSE", "FALSE",
			"FALSE", "FALSE", "FALSE", "FALSE", "FALSE", "FALSE",
			"FALSE", "FALSE", "FALSE",
			
			"1000.0", "1000.0", "1000.0",
			
			"0.5", "5.0",
			
			"10",
			
			""
		};
		
		private final static SettingsEnum[] STYLE = {COLOR_MESSAGE, COLOR_TIMESTAMP, COLOR_DOWNLOAD, COLOR_UPLOAD, COLOR_TOTAL};
		private final static SettingsEnum[] CHANGEABLE = {COLOR_FOREGROUND, COLOR_BACKGROUND, COLOR_DOWNLOAD, COLOR_UPLOAD, COLOR_TOTAL};
		private final static SettingsEnum[] INTERFACE = {INTERFACE_INTERFACE, INTERFACE_NETWORK};
		private final static SettingsEnum[] TIMER = {TIMER_MONITOR, TIMER_TRAFFIC};
		
		static SettingsEnum[] styles(){
			return STYLE;
		}

		static SettingsEnum[] changeable() {
			return CHANGEABLE;
		}
		
		static SettingsEnum[] timer(){
			return TIMER;
		}
		
		static SettingsEnum[] network(){
			return INTERFACE;
		}
		
		static String colorRGB(Color color){
			return Integer.toString(color.getRed())+" "+
					Integer.toString(color.getGreen())+" "+
					Integer.toString(color.getBlue());
		}
		
		static Color color(String rgb){
			String[] r = rgb.split(" ");
			return new Color(Integer.parseInt(r[0]), Integer.parseInt(r[1]), Integer.parseInt(r[2]));
			
		}
		
		private static Color defaultColor(SettingsEnum type){
			if(!type.getName().startsWith("COLOR_"))
				throw new IllegalArgumentException(type.toString()+" don't have any default color.");
			return color(DEFAULT[type.i]);
		}
		
		private String getName(){
			return super.toString();
		}
		
		@Override
		public String toString(){
			String name = super.toString();
			if(name.startsWith("COLOR_") || name.startsWith("TIMER_"))
				return name.substring(6, 7) + name.substring(7).toLowerCase();
			else if(name.startsWith("LIMIT_BOX_")){
				String[] a = name.split("_");
				return a[2].substring(0, 1) + a[2].substring(1).toLowerCase() + " - "
					+ a[3].substring(0, 1) + a[3].substring(1).toLowerCase();
			} else if(name.startsWith("INTERFACE_")){
				int index = name.indexOf("_")+1;
				return name.substring(index, index+1) + name.substring(index+1).toLowerCase();
			}
			return name;			
		}
	}

	@Override
	public void windowActivated(WindowEvent arg0) {}
	@Override
	public void windowClosed(WindowEvent arg0) {}
	@Override
	public void windowDeactivated(WindowEvent arg0) {}
	@Override
	public void windowDeiconified(WindowEvent arg0) {}
	@Override
	public void windowIconified(WindowEvent arg0) {}
	@Override
	public void windowOpened(WindowEvent arg0) {}
}